Atom Effects
Recoil Atom Effects #380 (proposal) どのようなものか?
Atom の Side-effects を定義するための新しいオプション
Atom に新しい値がセットされる操作をフックして、別の処理を実行できる (onSet)
いくつかの便利な API も提供される
resetSelf この Atom 自身をリセットする関数 (() => setSelf(new DefaultValue()) のシノニム)
これらの API は非同期処理の結果をセットことにも使える
今まではコンポーネントの React.useEffect を使う必要があったが、それを使わずに実現可能になった
Observable を Atom にすることが可能になった
できないこと
他の Recoil state を get/set できないよう意図的に設計されているものと思われる teramotodaiki.icon
GitHub 上でそれについて議論されている様子は見つけられなかったが、複雑すぎるので出来なくて正解だと思う
Type Definition
code:AtomEffect.js
type AtomOptions<T> = {
key: string,
default: T | RecoilValue<T> | Promise<T>,
...
effects: $ReadOnlyArray<AtomEffect<T>>,
};
// Effect is called the first time node is used with a <RecoilRoot>
type AtomEffect<T> = ({
node: RecoilState<T>,
trigger: 'get' | 'set',
// Can call either synchronously to initialize value or async to change it later
setSelf: (T | DefaultValue | (T => T | DefaultValue)) => void,
resetSelf: () => void,
getSnapshot: () => Snapshot,
// Subscribe to events
// Called when React batch ends, but before global RecoilTransactionObserver
onSet: ((newValue: T | DefaultValue, oldValue: T | DefaultValue) => void) => void,
}) => void | () => void; // Return optional cleanup handler
サンプルコード
これらの例は実際にテストしたわけではないので実際のリリースでは動かない可能性が高いです teramotodaiki.icon
例1 新しい値がセットされたときにサーバに値を同期する (かなり簡略化しています)
code:atomEffects_1.js
const syncAtom = atom({
key: 'syncAtom',
default: defaultValue,
effects: [({ onSet }) => {
onSet(value => saveValueToServer(value)); // サーバに値を同期する
}]
});
onSet フックできるというのがポイント
code:atomEffects_2.js
const value$ = new Observable( ... );
const obsAtom = atom({
key: 'obsAtom',
default: defaultValue,
effects: [({ setSelf }) => {
const subscription = value$.subscribe(value => setSelf(value));
return () => subscription.unsubscribe();
}]
});
setSelf は同期的でなくても使えるというのがポイント
setSelf は effects 関数の中で同期的にコールすることができる
内部的には duringInit というローカル変数が true になっており、全てのエフェクトをコールした時点で false になる
duringInit が true の間は store.fireNodeSubscriptions(writtenNodes, 'enqueue'); が実行されないので、おそらく値の変更は通知されないものと思われる teramotodaiki.icon
おそらく onSet のハンドラもコールされないものと思われる (どちらを先に書くかによらず)
Atom の get ハンドラの中でエフェクトの実行が完了しているため、同期的に setSelf をコールした場合はセットした値 (default ではなく、最初に setSelf された値) を返すと思われる
実装について
onSet は store.subscribeToTransactions で更新を Subscribe している
第二引数に key を与えると、特定の key を持つ Recoil node だけを Subscribe できる
Atom の default を Promise にした場合の Effects はまだ実装されていない
どうも onSet の引数が value だから、ということらしい teramotodaiki.icon
Loadable => Loadable という関数を与えられるなら可能だが…それは使い勝手が悪すぎる
余談
Atom に対する set が Redux の Dispatch に相当する
redux-observable と違うところは、
Atom Effects では単一の Action(Atom) に対してのみ Side-effects が許されている
redux-observable では RxJS の恩恵をフルに使って時系列的に Action をフィルタできる
Attom Effects では (おそらくだけど) onSet を再帰的にトリガーできない (本当か?🤔)
redux-observable では Action を pipe して別の Action を Dispatch することができる